package jmine.tec.rpc.server.base;

import java.io.Serializable;
import java.lang.reflect.TypeVariable;
import java.util.List;

import jmine.tec.rpc.common.entity.CrudEntityRest;
import jmine.tec.rpc.common.entity.EntityRepresentation;
import jmine.tec.rpc.server.representation.EntityUpdaterFromRepresentation;
import jmine.tec.utils.reflection.GenericTypeResolverImpl;
import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.dao.BaseDAO;
import bancosys.tec.persist.dao.BeanNotFoundException;

/**
 * Implementação base de EntityRestService
 * 
 * @author lundberg
 * @param <R> representation
 * @param <E> entity
 */
public class BaseEntityRestService<R extends EntityRepresentation, E extends Persistable> extends AbstractRestServiceImplementor implements
        CrudEntityRest<R> {

    private final Class<E> entityClass;

    private final Class<R> representationClass;

    private EntityUpdaterFromRepresentation entityUpdater;

    /**
     * Construtor default que le os generics fixos na classe. Lanca excecoes caso a classe nao tenha os tipos genericos fixos
     */
    @SuppressWarnings("unchecked")
    public BaseEntityRestService() {
        TypeVariable<Class<BaseEntityRestService>>[] typeParameters = BaseEntityRestService.class.getTypeParameters();
        this.representationClass =
                (Class<R>) GenericTypeResolverImpl.getInstance().resolveTypeForFixedGenericType(typeParameters[0], this.getClass());
        this.entityClass =
                (Class<E>) GenericTypeResolverImpl.getInstance().resolveTypeForFixedGenericType(typeParameters[1], this.getClass());
    }

    /**
     * Construtor
     * 
     * @param representationClass representationClass
     * @param entityClass entityClass
     */
    public BaseEntityRestService(Class<R> representationClass, Class<E> entityClass) {
        this.entityClass = entityClass;
        this.representationClass = representationClass;
    }

    /**
     * {@inheritDoc}
     */
    public List<R> findAll() {
        return this.toRepresentation(this.getDAO().findAll());
    }

    /**
     * {@inheritDoc}
     */
    public R findByIdentification(Serializable id) throws BeanNotFoundException {
        return this.toRepresentation(this.getDAO().findByPk(id));
    }

    /**
     * {@inheritDoc}
     */
    public R findByNaturalKey(Serializable naturalKey) throws BeanNotFoundException {
        return this.toRepresentation(this.getDAO().findByNaturalKey(naturalKey));
    }

    /**
     * {@inheritDoc}
     */
    public List<R> findByNaturalKeyLike(String naturalKey) {
        return this.toRepresentation(this.getDAO().findByLikeNaturalKey(naturalKey));
    }

    /**
     * {@inheritDoc}
     */
    public Class<R> getRepresentationClass() {
        return this.representationClass;
    }

    /**
     * Retorna a classe entidade representada.
     * 
     * @return Classe da entidade.
     */
    public Class<E> getEntityClass() {
        return this.entityClass;
    }

    /**
     * Converter o objeto para a representação utilizada pelo serviço.
     * 
     * @param object object
     * @return representation
     */
    protected R toRepresentation(E object) {
        return this.getRepresentationFactory().representObject(this.representationClass, object);
    }

    /**
     * Converter os objetos para aa representações utilizadas pelo serviço.
     * 
     * @param objects objects
     * @return representations
     */
    protected List<R> toRepresentation(List<? extends E> objects) {
        return this.getRepresentationFactory().representObjects(this.representationClass, objects);
    }

    /**
     * Obtém o DAO da entidade
     * 
     * @return dao
     */
    protected BaseDAO<E> getDAO() {
        return this.getDAOFactory().getGenericDAO(this.entityClass);
    }

    /**
     * {@inheritDoc}
     */
    public List<R> findAllOrderedByPk() {
        return this.toRepresentation(this.getDAO().findAllOrderedByPk());
    }

    /**
     * {@inheritDoc}
     */
    public List<? extends R> findByLikeNaturalKey(String naturalKeyPartial) {
        return this.toRepresentation(this.getDAO().findByLikeNaturalKey(naturalKeyPartial));
    }

    /**
     * {@inheritDoc}
     */
    public R findByPk(Serializable pk) throws BeanNotFoundException {
        return this.toRepresentation(this.getDAO().findByPk(pk));
    }

    /**
     * {@inheritDoc}
     */
    public Object getVersion(Serializable primaryKey) throws BeanNotFoundException {
        return this.getDAO().getVersion(primaryKey);
    }

    /**
     * {@inheritDoc}
     */
    public boolean hasVersion() {
        return this.getDAO().hasVersion();
    }

    /**
     * {@inheritDoc}
     */
    public R fectchIfNewer(Serializable primaryKey, Object previousVersion) throws BeanNotFoundException {
        return this.toRepresentation(this.getDAO().fectchIfNewer(primaryKey, previousVersion));
    }

    /**
     * {@inheritDoc}
     */
    public List<R> findAllByPks(Serializable[] pk) throws BeanNotFoundException {
        return this.toRepresentation(this.getDAO().findAllByPks(pk));
    }

    /**
     * Create a new Persistable object on the database using the information from the EntityRepresentation object.
     * 
     * @param representation of the object that will be saved.
     */
    public void save(R representation) {
        BaseDAO<E> daoFactory = this.getDAOFactory().getGenericDAO(this.entityClass);
        E persistableObject = daoFactory.createBean();
        this.entityUpdater.updateEntityFromRepresentation(persistableObject, representation);
        this.getDAO().save(persistableObject);
    }

    /**
     * Update an existent Persistable object with the information retrieved from the EntityRepresentation object.
     * 
     * @param representation of the object that will be updated.
     */
    public void update(R representation) {
        E persistableObject;
        try {
            persistableObject = this.getDAO().findByPk(representation.getPk());
            this.entityUpdater.updateEntityFromRepresentation(persistableObject, representation);
            persistableObject.getPersister().save();
        } catch (BeanNotFoundException e) {
            throw new RpcServerException(RestServiceErrorMessages.REPRESENTED_ENTITY_NOT_FOUND_ON_DATABASE_ERROR.create(), e);
        }
    }

    /**
     * Remove the Persistable object related to the EntityRepresentation from database.
     * 
     * @param representation object to be removed.
     */
    public void remove(R representation) {
        try {
            this.getDAO().findByPk(representation.getPk()).getPersister().remove();
        } catch (BeanNotFoundException e) {
            throw new RpcServerException(RestServiceErrorMessages.REPRESENTED_ENTITY_NOT_FOUND_ON_DATABASE_ERROR.create(), e);
        }
    }

    /**
     * Sets the persistable objects updater from the spring framework.
     * 
     * @param entityUpdater the bean instance.
     */
    public void setEntityUpdater(EntityUpdaterFromRepresentation entityUpdater) {
        this.entityUpdater = entityUpdater;
    }

}
